package com.common.sdk.net.connect.http;

import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;

import com.android.sohu.sdk.common.toolbox.ListUtils;
import com.android.sohu.sdk.common.toolbox.LogUtils;
import com.android.sohu.sdk.common.toolbox.NetworkUtils;
import com.android.sohu.sdk.common.toolbox.StringUtils;
import com.common.sdk.net.connect.http.center.ErrorType;
import com.common.sdk.net.connect.http.center.tools.HttpLog;
import com.common.sdk.net.connect.http.interceptor.MobileStatisticInterceptor;
import com.common.sdk.net.connect.http.interceptor.RetryInterceptor;
import com.common.sdk.net.connect.http.interceptor.SohuCacheInterceptor;
import com.common.sdk.net.connect.http.interceptor.SohuCacheNetInterceptor;
import com.common.sdk.net.connect.http.interceptor.StaticDomains;
import com.common.sdk.net.connect.http.model.AppBaseInfo;
import com.common.sdk.net.connect.http.model.HttpError;
import com.common.sdk.net.connect.http.model.NetworkParams;
import com.common.sdk.net.connect.http.model.OkHttpSession;
import com.common.sdk.net.connect.http.model.RequestTag;
import com.common.sdk.net.connect.http.util.BlockingLimitedStackQueue;
import com.common.sdk.net.connect.interfaces.IQueueFull;
import com.common.sdk.net.connect.interfaces.IResponseListener;
import com.common.sdk.net.connect.interfaces.IResultParser;

import org.json.JSONObject;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.Cache;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Dispatcher;
import okhttp3.FormBody;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.Util;

/**
 * Created by chengjiangsang116630 on 2018/1/16.
 */

public class OKHttpRequestPool implements IQueueFull<Runnable> {

    private static final String TAG = "OKHttpRequestPool";

    private static final String backupDomainStatisticUrl = "http://cdn.hd.sohu.com/datarelay/data/uploadFailureData?time=%d&domain=%s&netType=%s&signal=%d&pmodel=%s&osType=0";

    protected Context context;
    private static Handler mHandler = new Handler(Looper.getMainLooper());

    protected static final int MAX_ALIVE_TIME = 0;

    private OkHttpClient client;

    public OKHttpRequestPool() {
        this(null, null);
    }

    public OKHttpRequestPool(Context context, SohuNetConfig config) {

        this.context = context;
        if (config == null && context != null) {
            config = new SohuNetConfig.Builder(context).build();
        }

        Cache cache = null;
        Dispatcher dispatcher = null;
        List<Interceptor> interceptors = null;
        if (config != null) {
            cache = config.getCache();
            interceptors = config.getInterceptors();
            // 使用自定义的线程池， 使用自定义的队列
            BlockingQueue<Runnable> mHttpPrepareQueue = new BlockingLimitedStackQueue<Runnable>(config.poolQueueSize, this);
            ExecutorService executorService = new ThreadPoolExecutor(config.poolSize, config.poolSize, MAX_ALIVE_TIME, TimeUnit.SECONDS,
                    mHttpPrepareQueue, Util.threadFactory("OkHttp Dispatcher", false));
            dispatcher = new Dispatcher(executorService);
            // 参见文档 《OkHttp自定义线程池特性》
            dispatcher.setMaxRequestsPerHost(2 * (config.poolQueueSize + config.poolSize + 1));
        }

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        if (dispatcher != null) {
            builder.dispatcher(dispatcher);
        }

        if (interceptors != null && interceptors.size() > 0) {
            for(Interceptor interceptor : interceptors) {
                builder.addInterceptor(interceptor);
            }
        }

        // 注意拦截器的顺序不可随意更改
        builder.cache(cache)
                .addInterceptor(new MobileStatisticInterceptor())
                .addInterceptor(new RetryInterceptor())
                .addInterceptor(new SohuCacheInterceptor())
                .addNetworkInterceptor(new SohuCacheNetInterceptor())
                .connectTimeout(NetworkParams.TIMEOUT_MS, TimeUnit.MILLISECONDS)
                .readTimeout(NetworkParams.TIMEOUT_MS, TimeUnit.MILLISECONDS);

        setSSL(builder);

        client = builder.build();

    }

    private static void setSSL(OkHttpClient.Builder client) {
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[]{new X509TrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }

                @Override
                public void checkClientTrusted(X509Certificate[] chain,
                                               String authType) throws CertificateException {

                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain,
                                               String authType) throws CertificateException {

                }
            }}, new SecureRandom());
            client.sslSocketFactory(sc.getSocketFactory());
            client.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
        } catch (Exception e) {
            HttpLog.error(e);
        }
    }

    /**
     * Runs the specified action on the UI thread. If the current thread is not
     * the UI thread, the action is posted to the event queue of the UI thread.
     *
     * @param action the action to run on the UI thread
     */
    public final void runOnUiThread(Runnable action) {
        mHandler.post(action);
    }

    public Context getContext() {
        return context;
    }

    /**
     * 谁调用此方法需要自己关闭response
     * @param request
     * @return
     */
    Response execute(Request request) {
        Response response = null;
        try {
            response = realExecute(request);
            if (response == null) {
                // 进行域名替换后重新请求，不回调出错
                Request newRequest = buildRequestDomainReplaced(request);
                if (newRequest != null) {

                    response = client.newCall(newRequest).execute();

                    // 发送统计点
                    Request statisticRequest = getBackupStatisticRequest(request);
                    enqueue(statisticRequest, null);
                }
            }
        } catch (Exception e) {
            HttpLog.error(request, "backdomain sync request fail : " + e.getMessage(), e);
        }
        return response;
    }

    private Response realExecute(Request request) {
        Response response = null;
        try {
            HttpLog.debug(request, "startSyncRequest : ");
            response = client.newCall(request).execute();
            HttpLog.debug(request, "startSyncRequest , response is " + response);
        } catch (Exception e) {
            HttpLog.error(request, "sync request fail : " + e.getMessage(), e);
        }
        return response;
    }

    void enqueue(final Request request, final IResultParser parser) {
        Object tag = request.tag();
        IResponseListener responseListener = null;
        if (tag != null && tag instanceof RequestTag) {
            RequestTag requestTag = (RequestTag) tag;
            responseListener = requestTag.getListener();
        }

        final IResponseListener finalListener = responseListener;

        try {

            HttpLog.debug(request, "startASyncRequest : ");

            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    if (finalListener == null) {
                        HttpLog.debug(request, " listener is null");
                        return;
                    }

                    HttpError error = new HttpError(ErrorType.ERROR_DEFAULT_NET_FAILED, "Okhttp回调出错", e);
                    OkHttpSession session = buildSessionData(request, null);

                    failCallbackInUIThread(finalListener, parser, error, session, call);
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {

                    if (finalListener == null) {
                        HttpLog.debug(request, " listener is null");
                        return;
                    }

                    OkHttpSession session = buildSessionData(request, response);

                    if (response != null) {
                        try {
                            ResponseBody body = response.body();
                            if (body == null) {
                                HttpError error = new HttpError(ErrorType.ERROR_SERVICE_EXCEPTION, "返回body数据为空");
                                failCallbackInUIThread(finalListener, parser, error, session, call);
                            } else {
                                Object obj = parser.parse(response, session.getJson());
                                if (obj != null) {
                                    HttpLog.debug(request, " response success : " + response);
                                    successCallbackInUIThread(finalListener, obj, session, call);
                                } else {
                                    HttpError error = new HttpError(ErrorType.ERROR_DATA_PARSE, "数据解析返回结果为空");
                                    failCallbackInUIThread(finalListener, parser, error, session, call);
                                }
                            }

                        } catch (Exception e) {
                            HttpLog.error(request, e);
                            HttpError error = new HttpError(ErrorType.ERROR_DATA_PARSE, "数据解析出错", e);
                            failCallbackInUIThread(finalListener, parser, error, session, call);
                        }
                    } else {
                        HttpLog.debug(request, "response is null");
                        HttpError error = new HttpError(ErrorType.ERROR_SERVICE_EXCEPTION, "response为空");
                        failCallbackInUIThread(finalListener, parser, error, session, call);
                    }
                }
            });

        } catch (Exception | Error e) {
            HttpError error = new HttpError(ErrorType.ERROR_CLIENT_PARAM_INVALID, e.getMessage(), e);
            OkHttpSession session = buildSessionData(request, null);
            failCallbackInUIThread(responseListener, parser, error, session, null);
        }

    }

    private void successCallbackInUIThread(final IResponseListener responseListener, final Object obj, final OkHttpSession session, final Call call) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {

                // 如果已取消，则不回调出错，而是回调取消
                if (call != null && call.isCanceled()) {
                    HttpLog.debug(session.getRequest(), " request canceled ");
                    cancelCallbackInUIThread(responseListener, session);
                    return;
                }

                if (responseListener != null) {
                    responseListener.onSuccess(obj, session);
                }
            }
        });
    }

    private void failCallbackInUIThread(final IResponseListener responseListener, final IResultParser parser, final HttpError error, final OkHttpSession session, final Call call) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {

                // 如果已取消，则不回调出错，而是回调取消
                if (call != null && call.isCanceled()) {
                    HttpLog.debug(session.getRequest(), " request canceled ");
                    cancelCallbackInUIThread(responseListener, session);
                    return;
                }
                HttpLog.error(session.getRequest(), error.getE());
                HttpLog.error(session.getRequest(), "request fail : " + error.getDesc());

                // 特定错误下不进行域名替换
                ErrorType errorType = error.getErrorType();
                if (parser != null
                        && errorType != ErrorType.ERROR_CLIENT_PARAM_INVALID
                        && errorType != ErrorType.ERROR_QUEUE_FULL
                        && errorType != ErrorType.ERROR_NO_NET
                        ) {
                    // 进行域名替换后重新请求，不回调出错
                    if (ifNeedReplaceDomain(session.getRequest(), parser)) {
                        return;
                    }
                }

                if (responseListener != null) {
                    responseListener.onFailure(error, session);
                }
                // 上报错误信息
                sendErrorReport(session.getRequest(), session.getResponse());
            }
        });
    }

    private void cancelCallbackInUIThread(final IResponseListener responseListener, final OkHttpSession session) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (responseListener != null) {
                    responseListener.onCancelled(session);
                }
            }
        });
    }

    @Override
    public void queueFullWhenOffer(Runnable dataDispatcher) {

        try {
            Class<?> cls = Class.forName("okhttp3.RealCall$AsyncCall");
            Method request = cls.getDeclaredMethod("request");
            request.setAccessible(true);
            Object obj = request.invoke(dataDispatcher);
            if (obj != null) {
                Request originRequest = (Request) obj;
                RequestTag requestTag = (RequestTag) originRequest.tag();
                OkHttpSession session = new OkHttpSession();
                session.setRequest(originRequest);
                HttpError error = new HttpError(ErrorType.ERROR_QUEUE_FULL, "队列满");
                failCallbackInUIThread(requestTag.getListener(), null, error, session, null);
            }

        } catch (Exception e) {
            LogUtils.e(TAG, e);
        }

    }


    private OkHttpSession buildSessionData(Request request, Response response) {
        OkHttpSession session = new OkHttpSession();
        session.setRequest(request);
        session.setResponse(response);

        setSessionInfo(session, response);

        return session;
    }


    private void setSessionInfo(OkHttpSession session, Response response) {

        if (response == null || response.body() == null) {
            return;
        }

        try {

            String json = response.body().string();
            session.setJson(json);

            if (StringUtils.isNotBlank(json)) {

                JSONObject jsonObject = new JSONObject(json);
                if (jsonObject.has("status")) {
                    session.setCode(jsonObject.getInt("status"));
                }

                if (jsonObject.has("statusText")) {
                    session.setMsg(jsonObject.getString("statusText"));
                }

                if (jsonObject.has("errorCode")) {
                    session.setErrorCode(jsonObject.getInt("errorCode"));
                }

            }
        } catch (Exception e) {
            HttpLog.error(e);
        }
    }

    private boolean ifNeedReplaceDomain(Request request, IResultParser parser) {

        Request newRequest = buildRequestDomainReplaced(request);

        if (newRequest != null) {
            // 原始url访问失败，换备用域名访问
            enqueue(newRequest, parser);

            // 发送统计点
            Request statisticRequest = getBackupStatisticRequest(request);
            enqueue(statisticRequest, null);

            return true;
        }
        return false;
    }

    private Request buildRequestDomainReplaced(Request request) {
        String url = request.url().toString();
        String host = getRequestHost(request);
        if (StringUtils.isBlank(host)) {
            return null;
        }

        if (StringUtils.isBlank(host)) {
            return null;
        }

        if (StringUtils.isBlank(OkhttpManager.getBackupDomain())) {
            return null;
        }

        if (ListUtils.isEmpty(OkhttpManager.getReplacableDomains()) || !OkhttpManager.getReplacableDomains().contains(host)) {
            return null;
        }
        if (staticDomainReplace()) {
            url = StaticDomains.replaceStaticDomain(url);
        }

        url = url.replace(host, OkhttpManager.getBackupDomain());

        request = request.newBuilder().url(url).build();

        LogUtils.d(TAG + "SCJ",  "request after replace : " + request.url().toString());

        return request;
    }

    private String getRequestHost(Request request) {
        if (request == null) {
            return null;
        }
        if (staticDomainReplace()) {
            Request newRequest = request.newBuilder().url(StaticDomains.replaceStaticDomain(request.url().toString())).build();
            return newRequest.url().host();
        } else {
            return request.url().host();
        }
    }

    private Request getBackupStatisticRequest(Request request) {
        String netType = NetworkUtils.getNetworkStringByType(NetworkUtils.getNetworkType(context));
        int signal = getSignal();
        String pmodel = Build.MODEL;
        Request statisticRequest = new Request.Builder()
                .url(String.format(backupDomainStatisticUrl, System.currentTimeMillis()/1000, getRequestHost(request), netType, signal, pmodel))
                .get()
                .build();

        return statisticRequest;
    }

    private int getSignal() {
        if (NetworkUtils.isWifi(context)) {
            return NetworkUtils.getWIFISignal(context);
        } else {
            return NetworkUtils.getDBM();
        }
    }

    private void sendErrorReport(Request originRequest, Response response) {

        try {

            AppBaseInfo baseInfo = OkhttpManager.getBaseInfo();
            if (baseInfo == null) {
                return;
            }
            String parterNO = baseInfo.getParterNo();
            // 如果不是680或6581两个渠道，则不上报
            if (!("680".equals(parterNO) || "6581".equals(parterNO))) {
                return;
            }

            String url = originRequest.url().url().toString();
            URI uri = URI.create(url);
            String host = uri.getHost();
            // 如果是接口本身出错，则不发送
            if ("u.hd.sohu.com".equals(host) || url.startsWith("http://u.hd.sohu.com")) {
                return;
            }

            if (StringUtils.isBlank(baseInfo.getUid())) {
                return;
            }

            FormBody.Builder formBodyBuilder = new FormBody.Builder()
                    .add("uuid", baseInfo.getUid())
                    .add("domian", host);

            if (response != null) {

                formBodyBuilder.add("statusCode", String.valueOf(response.code()));

                JSONObject jsonObject = new JSONObject();
                jsonObject.put("originUrl", originRequest.url().url().toString());

                ResponseBody body = response.body();
                if (body != null) {
                    String bodyStr = body.string();
                    jsonObject.put("res", bodyStr);
                }

                Map<String, List<String>> headers = response.headers().toMultimap();
                Set<String> keys = headers.keySet();
                JSONObject header = new JSONObject();
                Iterator<String> it = keys.iterator();
                while (it.hasNext()) {
                    String key = it.next();
                    List<String> values = headers.get(key);
                    header.put(key, values.toString());
                }
                jsonObject.put("headers", header);

                formBodyBuilder.add("info", jsonObject.toString());
            }

            RequestBody requestBody = formBodyBuilder.build();

            Request request = new Request.Builder()
                    .url("http://u.hd.sohu.com/post")
                    .post(requestBody)
                    .build();


            enqueue(request, null);
        } catch (Exception e) {
            LogUtils.e(TAG, e);
        }

    }

    public void cancel(String tag) {
        if (client == null || StringUtils.isBlank(tag)) {
            return;
        }

        for(Call call : client.dispatcher().queuedCalls()) {
            cancelTagCall(call, tag);
        }

        for(Call call : client.dispatcher().runningCalls()) {
            cancelTagCall(call, tag);
        }
    }

    private void cancelTagCall(Call call, String tag) {
        Object obj = call.request().tag();
        if (obj != null) {
            if (obj instanceof RequestTag) {
                RequestTag requestTag = (RequestTag) obj;
                if (tag.equals(requestTag.getTag())) {
                    call.cancel();
                }
            }
        }
    }

    public void cancelAll() {
        if (client != null) {
            client.dispatcher().cancelAll();
        }
    }

    public static boolean staticDomainReplace() {
        return true;
    }

}
